PatsFans.com Menu
PatsFans.com - The Hub For New England Patriots Fans
PatsFans.com - The Hub For New England Patriots Fans
New England Patriots > Patriots Blog

Plenty of Ways To Slice the Vinatieri Blame Pie

Kevin Rousseau
Kevin Rousseau on Twitter
Mar 21, 2006 at 5:00am ET

Hurt. Anger. Disillusionment. Betrayal.

In the coming days, such words will be tossed around to describe the feelings of Patriot Nation on the news that one of the most popular Boston sports figures of all time has left us for a hated arch-rival.

If you travel at all around this country, you learn that we are thought of in this region as being petty, self-centered and revenge-minded when it comes to our sports.

"And on these charges brought against you in this case, Mr. Rousseau, how do you plead?"

"Guilty as charged, your honor."

To an outsider, we may indeed seem self-absorbed but frankly that's because we actually care what happens to our sports teams. There is arguably no greater sports city on the planet. As I see it, this is largely due to the emotional connections to the past that develop over time between players and fans in this area. When was the last time Johnny Pesky, Ray Bourque or Bobby Orr paid for a meal around here?

So when a player who we have become endearingly attached to - in this case, Vinatieri - decides to spurn us, the Ginsu knives quickly come out of the drawer. "How could he do this to us? Why wouldn't he want to stay here in the Hub of Sports Nirvana?" we resentfully ask.

#4 is responsible for some of the most joyous moments in my life that don't involve the birth of my children. Watching that kick keep floating as if guided by Divine intervention against the Raiders. The shock of seeing the Patriots win the greatest Super Bowl of them all in New Orleans in person. The countless game winning kicks in the fourth quarter throughout the years. These are the bookmarks in my life.

Man, the more I reflect on all of them, I could write a column on just those moments. Yet now, these memories are somewhat a little less than perfect. Perhaps that is what is turning in my stomach today.

There are plenty of ways to slice up the Adam-Bolts-To-The-Colts blame pie and let's start with the greatest clutch kicker of all time.

I hope that extra million or two dollars that he will earn in the middle of nowhere (read: Indiana) will be worth it to him fifteen years from now. In exchange for a more lucrative bird in hand now, he has abdicated any ability to sell me a slice of pizza, a checking account or a pair of sneakers here in New England after his career is over in football. Those outrageously priced autograph-signing sessions might be a little harder to come by as well.

By going to the Colts, he has thrown away any remaining goodwill that may have still existed towards him if he had flown off to Green Bay or Dallas. This is not the equivalent of Bobby Orr jetting off to the Blackhawks. This is the equivalent of moving from Ali to Frazier's camp.

But the largest piece of blame pie is reserved for the New England Patriots organization and the perceived arrogance that has finally caught up to them. For years, they were praised for not overpaying and living to tell about it after letting players like Lawyer Milloy go that weren't part of the "Patriot Way."

Despite being a whopping $20 million under the salary cap, we have had radio silence from Foxboro so far in free agency. It's difficult to perceive a scenario whereby whoever they bring in via free agency is going to make us think that this will be a better team on paper come September than the one that imploded in Denver back in January.

Let's take a quick look around the team: A question mark at running back, wide receiver, secondary and now the kicking game. Oh, and don't forget a new defensive coordinator.

Beautiful.

In the coming days the Patriots machine will spin out the propaganda about how they saved money and how Vinatieri was on the downside of his career, etc.

Don't buy it for a minute.

If they think they saved a million dollars by letting him go, humor me and keep your binoculars focused on Bob Kraft's luxury suite when he sweats a potential home playoff gate being in the hands of Paul Edinger during a late December game.

This feels like the beginning of the end, doesn't it?

Once one player didn't take the hometown discount, the wheels began to fall off the cart. Next it will be Stephen Neal, then Tom Ashworth and then (Lord-forbid) Troy Brown.

It's days like today when you think for a moment that perhaps you would be better off spending your free time following the local jazz scene or getting hooked on impressionist paintings instead of watching professional sports. If we did, then we wouldn't have to feel the way we are right now.

The truth, though, is that the valleys of days like today make the wonderful triumphs of tomorrow that much better.

Let's just hope that in the Patriots case, it won't be as long of a wait as I now think it's going to be.


(function() { document.addEventListener('DOMContentLoaded', function() { var mediaBtn = document.getElementById('pfeai-media-button'); if (mediaBtn) { mediaBtn.addEventListener('click', function(e) { e.preventDefault(); if (window.PFEditorialAI) { PFEditorialAI.togglePanel(); } }); } }); if (typeof tinymce === 'undefined') { return; } tinymce.create('tinymce.plugins.PFEditorialAI', { init: function(editor, url) { editor.on('init', function() { PFEditorialAI.createPanel(editor); PFEditorialAI.bindTooltipEvents(); PFEditorialAI.preloadSavedData(); }); }, getInfo: function() { return { longname: 'PF Editorial AI', author: 'PatsFans + AI', version: '5.2.7' }; } }); tinymce.PluginManager.add('pfeai_plugin', tinymce.plugins.PFEditorialAI); window.PFEditorialAI = { panelCreated: false, panelEl: null, loadingEl: null, analysisEl: null, titlesEl: null, ideasEl: null, transcriptEl: null, socialEl: null, savedEl: null, factsEl: null, linksEl: null, isMinimized: false, // Cache window tracking cacheWindowStart: null, cacheWindowTimer: null, cacheWindowDuration: 4 * 60 * 1000, // 4 minutes (conservative estimate) tasksInWindow: 0, /** * SECURITY: Escape HTML to prevent XSS * This function must be used for ALL user/API data inserted into the DOM */ escapeHtml: function(str) { if (str === null || str === undefined) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }, /** * SECURITY: Sanitize for textarea/input values */ escapeAttr: function(str) { if (str === null || str === undefined) return ''; return String(str) .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); }, /** * Cache Window Indicator - shows when implicit caching is active */ startCacheWindow: function(fromCache) { var self = this; // If result was from app cache, don't start/extend the API cache window if (fromCache) return; this.tasksInWindow++; // Start or extend the cache window this.cacheWindowStart = Date.now(); // Clear existing timer if (this.cacheWindowTimer) { clearInterval(this.cacheWindowTimer); } // Update indicator immediately this.updateCacheIndicator(); // Update every second this.cacheWindowTimer = setInterval(function() { self.updateCacheIndicator(); }, 1000); }, updateCacheIndicator: function() { var indicator = document.getElementById('pfeai-cache-indicator'); if (!indicator) return; if (!this.cacheWindowStart) { indicator.style.display = 'none'; return; } var elapsed = Date.now() - this.cacheWindowStart; var remaining = this.cacheWindowDuration - elapsed; if (remaining <= 0) { // Cache window expired indicator.style.display = 'none'; this.cacheWindowStart = null; this.tasksInWindow = 0; if (this.cacheWindowTimer) { clearInterval(this.cacheWindowTimer); this.cacheWindowTimer = null; } return; } // Show indicator indicator.style.display = 'block'; var minutes = Math.floor(remaining / 60000); var seconds = Math.floor((remaining % 60000) / 1000); var timeStr = minutes + ':' + (seconds < 10 ? '0' : '') + seconds; var savingsText = this.tasksInWindow > 1 ? ' · ' + (this.tasksInWindow - 1) + ' task' + (this.tasksInWindow > 2 ? 's' : '') + ' cached' : ''; indicator.innerHTML = '⚡ Cache active: ' + timeStr + ' remaining' + savingsText + '
Run more tasks now to save tokens'; }, getProvider: function() { var select = document.getElementById('pfeai-provider-select'); return select ? select.value : 'gemini'; }, getSafeContent: function(editor) { try { if (editor && !editor.isHidden()) return editor.getContent({ format: 'raw' }); var textArea = document.getElementById('content'); return textArea ? textArea.value : ''; } catch (e) { return ''; } }, getSafeText: function(editor) { try { if (editor && !editor.isHidden()) return editor.getContent({ format: 'text' }); var textArea = document.getElementById('content'); return textArea ? textArea.value : ''; } catch (e) { return ''; } }, createPanel: function(editor) { if (this.panelCreated) return; // FIX: Inject Layout Push Styles directly to ensure they work var style = document.createElement('style'); style.innerHTML = 'body.pfeai-panel-open #wpbody-content { margin-right: 360px !important; transition: margin-right 0.3s ease; } body.pfeai-panel-open .pfeai-panel { right: 0; box-shadow: -2px 0 5px rgba(0,0,0,0.1); }'; document.head.appendChild(style); var panel = document.createElement('div'); panel.id = 'pfeai-panel'; var html = ''; html += '
'; html += '
'; html += '
'; html += '
'; html += ' '; html += '
'; html += '
PF Editorial AI
'; html += '
'; html += ' '; html += '
'; html += '
'; html += '
'; html += '
'; html += ' '; html += ' '; html += '
'; html += '
'; html += '
Click to expand
'; html += '
'; html += ' '; html += '
'; html += '
'; if (PFEAI.saved_data.clipboard) { html += '
'; html += ''; html += ''; html += '
'; } html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; // Only show Clear Topics button if there's cached ideas data if (PFEAI.saved_data.ideas && PFEAI.saved_data.ideas.ideas) { html += '
'; // FIX: Set both to flex:1 and width:auto to make them equal size html += ''; html += ''; html += '
'; } else { html += ' '; } html += ' '; html += '
'; html += '
'; html += ' '; html += '
'; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; if (PFEAI.saved_data.clipboard) { html += ' '; } html += '
'; html += '
'; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += ' '; html += '
'; html += '
'; html += '
'; panel.innerHTML = html; document.body.appendChild(panel); this.panelEl = panel; this.loadingEl = panel.querySelector('.pfeai-loading'); this.analysisEl = { highlights: panel.querySelector('.pfeai-tab-content[data-tab="highlights"]'), suggestions: panel.querySelector('.pfeai-tab-content[data-tab="suggestions"]'), gaps: panel.querySelector('.pfeai-tab-content[data-tab="gaps"]') }; this.titlesEl = panel.querySelector('.pfeai-tab-content[data-tab="titles"]'); this.seoEl = panel.querySelector('.pfeai-tab-content[data-tab="seo"]'); this.ideasEl = panel.querySelector('.pfeai-tab-content[data-tab="ideas"]'); this.transcriptEl = panel.querySelector('.pfeai-tab-content[data-tab="transcript"]'); this.socialEl = panel.querySelector('.pfeai-tab-content[data-tab="social"]'); this.savedEl = panel.querySelector('.pfeai-tab-content[data-tab="saved"]'); this.factsEl = panel.querySelector('.pfeai-tab-content[data-tab="facts"]'); this.linksEl = panel.querySelector('.pfeai-tab-content[data-tab="links"]'); this.faqEl = panel.querySelector('.pfeai-tab-content[data-tab="faq"]'); panel.querySelector('.pfeai-faq-btn').addEventListener('click', function() { self.runFAQ(editor); }); panel.querySelector('#btn-internal-links').addEventListener('click', function() { var btnText = this.innerText; if (btnText.indexOf('View Links') !== -1) { self.showResults('links'); } else { self.runInternalLinks(editor); } }); // Add this new listener for the Search button panel.querySelector('#btn-search').addEventListener('click', function() { self.showResults('search'); }); var self = this; panel.querySelector('.pfeai-close').addEventListener('click', function(e) { e.stopPropagation(); self.togglePanel(false); }); panel.querySelector('.pfeai-minimize-btn').addEventListener('click', function(e) { e.stopPropagation(); self.toggleMinimize(); }); panel.querySelector('.pfeai-back-btn').addEventListener('click', function(e) { e.stopPropagation(); self.showMenu(); }); var viewSavedBtn = document.getElementById('btn-view-saved'); if (viewSavedBtn) { viewSavedBtn.addEventListener('click', function() { self.showResults('saved'); }); } var clearSavedBtn = document.getElementById('btn-clear-saved'); if (clearSavedBtn) { clearSavedBtn.addEventListener('click', function() { if (confirm('Are you sure you want to clear your saved draft? This cannot be undone.')) { self.clearClipboard(); } }); } panel.querySelector('#btn-scan').addEventListener('click', function() { var btnText = this.innerText; if (btnText.indexOf('View Analysis') !== -1) { self.showResults('highlights'); } else { self.runAnalysis(editor); } }); panel.querySelector('#btn-social').addEventListener('click', function() { var btnText = this.innerText; if (btnText.indexOf('View Social') !== -1) { self.showResults('social'); } else { self.runSocialPack(editor); } }); panel.querySelector('#btn-transcript').addEventListener('click', function() { var btnText = this.innerText; if (btnText.indexOf('View Transcript') !== -1) { self.showResults('transcript'); } else { self.runTranscriptScan(editor); } }); panel.querySelector('#btn-titles').addEventListener('click', function() { var btnText = this.innerText; if (btnText.indexOf('View Titles') !== -1) { self.showResults('titles'); } else { self.runTitles(editor); } }); panel.querySelector('#btn-ideas').addEventListener('click', function() { var btnText = this.innerText; if (btnText.indexOf('View Topics') !== -1) { self.showResults('ideas'); } else { self.runArticleIdeas(editor); } }); // Clear Ideas Cache button (only exists if there's cached data) var clearIdeasBtn = panel.querySelector('#btn-clear-ideas'); if (clearIdeasBtn) { clearIdeasBtn.addEventListener('click', function() { if (confirm('Clear cached Topic Ideas? This will reset the Topics tab.')) { self.clearIdeasCache(); } }); } panel.querySelector('.pfeai-tagline-btn').addEventListener('click', function() { self.runTagline(editor); }); panel.querySelector('.pfeai-tags-btn').addEventListener('click', function() { self.runTagSuggestions(editor); }); panel.querySelector('.pfeai-meta-btn').addEventListener('click', function() { self.runMetaDescFromMenu(editor); }); panel.querySelectorAll('.pfeai-tab').forEach(function(btn) { btn.addEventListener('click', function() { self.switchTab(this.getAttribute('data-tab')); }); }); this.panelCreated = true; }, runSocialPack: function(editor, forceRefresh) { var self = this; var contentHtml = this.getSafeContent(editor); var title = (document.getElementById('title') || {}).value || ''; if (!title) { alert('Please enter a title.'); return; } this.showResults('social'); this.setLoading(true); fetch(PFEAI.rest_url + 'social-pack?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ content: contentHtml, title: title, post_id: PFEAI.post_id, provider: this.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}).then(function(json){ self.setLoading(false); if(json.tweets) { self.renderSocialPack(json); var timestamp = json.generated_at || Date.now()/1000; self.setCachedState('btn-social', 'View Social Posts', 'social', timestamp, json.from_cache); self.startCacheWindow(json.from_cache); } else if (json.code) { // Handle error response self.socialEl.innerHTML = '

Error: ' + self.escapeHtml(json.message) + '

'; } }).catch(function(e) { self.setLoading(false); console.error(e); }); }, renderSocialPack: function(data) { var self = this; this.socialEl.innerHTML = '

Social Posts

'; (data.tweets || []).forEach(function(t, idx) { self.createSocialCard('Twitter / X ('+(idx+1)+')', t, self.socialEl); }); if (data.facebook) self.createSocialCard('Facebook', data.facebook, self.socialEl); if (data.meta_desc) self.createSocialCard('Meta Description (SEO)', data.meta_desc, self.socialEl); }, /** * SECURITY FIX: Use textContent instead of innerHTML for user data */ createSocialCard: function(label, text, container) { var card = document.createElement('div'); card.className = 'pfeai-card'; card.style.background = '#f5f5f5'; var labelEl = document.createElement('strong'); labelEl.textContent = label; card.appendChild(labelEl); var textEl = document.createElement('p'); textEl.style.fontSize = '12px'; textEl.style.marginBottom = '5px'; textEl.textContent = text; // SECURITY: textContent escapes HTML card.appendChild(textEl); var copyBtn = document.createElement('button'); copyBtn.className = 'button button-small'; copyBtn.textContent = 'Copy'; copyBtn.onclick = function() { navigator.clipboard.writeText(text); copyBtn.textContent = 'Copied!'; setTimeout(function(){ copyBtn.textContent = 'Copy'; }, 1500); }; card.appendChild(copyBtn); container.appendChild(card); }, runAnalysis: function(editor, forceRefresh) { var self = this; var contentHtml = this.getSafeContent(editor); var title = (document.getElementById('title') || {}).value || ''; if (!title) { alert('Please enter a title.'); return; } this.showResults('highlights'); this.setLoading(true); fetch(PFEAI.rest_url + 'analyze?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ content: contentHtml, title: title, post_id: PFEAI.post_id, provider: this.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}).then(function(json){ self.setLoading(false); if(json.highlights) { self.renderAnalysis(json); var timestamp = json.generated_at || Date.now()/1000; self.setCachedState('btn-scan', 'View Analysis Results', 'highlights', timestamp, json.from_cache); self.startCacheWindow(json.from_cache); } else if (json.code) { self.analysisEl.highlights.innerHTML = '

Error: ' + self.escapeHtml(json.message) + '

'; } }).catch(function(e) { self.setLoading(false); console.error(e); }); }, runTitles: function(editor, forceRefresh) { var self = this; var contentHtml = this.getSafeContent(editor); var title = (document.getElementById('title') || {}).value || ''; this.showResults('titles'); this.setLoading(true); fetch(PFEAI.rest_url + 'seo-titles?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ content: contentHtml, title: title, post_id: PFEAI.post_id, provider: this.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}).then(function(json){ self.setLoading(false); if(json.titles) { self.renderTitles(json); var timestamp = json.generated_at || Date.now()/1000; self.setCachedState('btn-titles', 'View Titles', 'titles', timestamp, json.from_cache); self.startCacheWindow(json.from_cache); } else if (json.code) { self.titlesEl.innerHTML = '

Error: ' + self.escapeHtml(json.message) + '

'; } }); }, runTranscriptScan: function(editor, forceRefresh) { var self = this; var contentText = this.getSafeText(editor); if (contentText.length < 50) { alert('Please paste transcript.'); return; } this.showResults('transcript'); this.setLoading(true); fetch(PFEAI.rest_url + 'transcript-scan?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ content: contentText, post_id: PFEAI.post_id, provider: this.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}) .then(function(json){ self.setLoading(false); console.log('Transcript scan response:', json); // Handle the response - could be {suggestions: [...]} or a single object or bad cache var suggestions = null; if (json.suggestions && Array.isArray(json.suggestions)) { // Correct format: {suggestions: [{...}, {...}]} suggestions = json.suggestions; } else if (json.title && json.context) { // Bad format: single object at root (legacy cache issue) // Wrap it in an array suggestions = [json]; console.warn('Transcript response was single object, wrapped in array. Consider clearing cache.'); } if (suggestions && suggestions.length > 0) { self.renderTranscriptSuggestions(suggestions, contentText); var timestamp = json.generated_at || Date.now()/1000; self.setCachedState('btn-transcript', 'View Transcript Results', 'transcript', timestamp, json.from_cache); self.startCacheWindow(json.from_cache); } else if (json.code) { self.transcriptEl.innerHTML = '

Error: ' + self.escapeHtml(json.message) + '

'; } else { console.error('Unexpected transcript response:', json); self.transcriptEl.innerHTML = '

Unexpected response format. Please click "Rescan" to refresh.

'; } }) .catch(function(err) { self.setLoading(false); console.error('Transcript scan error:', err); self.transcriptEl.innerHTML = '

Request failed: ' + self.escapeHtml(err.message) + '

'; }); }, /** * IMPROVED: Added better error handling and timeout management for transcript outline */ runTranscriptOutline: function(title, content, clickedCard) { var self = this; var resDiv = document.getElementById('pfeai-transcript-result'); // Show loading state with timeout warning resDiv.innerHTML = '

Generating outline...

This may take up to 30 seconds for longer transcripts.

'; // Validate inputs if (!title) { resDiv.innerHTML = '

Error: No headline selected. Please click a headline above.

'; return; } if (!content || content.length < 50) { resDiv.innerHTML = '

Error: Transcript content is too short or missing.

'; return; } // Create abort controller for timeout var controller = new AbortController(); var timeoutId = setTimeout(function() { controller.abort(); }, 180000); // 3 minute timeout (matching server timeout) fetch(PFEAI.rest_url + 'transcript-outline?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ selected_title: title, content: content, provider: this.getProvider() }), signal: controller.signal }) .then(function(r) { clearTimeout(timeoutId); if (!r.ok) { throw new Error('Server returned status ' + r.status); } return r.json(); }) .then(function(json) { console.log('Transcript outline response:', json); // Check for error response if (json.code) { resDiv.innerHTML = '

Error: ' + self.escapeHtml(json.message) + '

Try selecting a different headline or click Rescan.

'; return; } // Check for required field (blurb) if (json.blurb) { self.renderTranscriptOutlineResult(json, title); } else { console.error('Missing blurb in response:', json); resDiv.innerHTML = '

AI response was incomplete. Please try again.

Response received but missing required content.

'; } }) .catch(function(err) { clearTimeout(timeoutId); console.error('Transcript outline error:', err); var errorMsg = 'Error generating outline.'; if (err.name === 'AbortError') { errorMsg = 'Request timed out. The transcript may be too long.'; } else if (err.message) { errorMsg = err.message; } resDiv.innerHTML = '

' + self.escapeHtml(errorMsg) + '

Try again or select a different headline.

'; }); }, runArticleIdeas: function(editor, forceRefresh) { var self = this; var title = (document.getElementById('title') || {}).value || ''; var contentText = this.getSafeText(editor).trim(); // Only block if not forcing refresh AND there's content if (!forceRefresh && (title.length > 0 || contentText.length > 5)) { alert('To generate fresh Topic Inspiration, please clear the Title and Content editor first.'); return; } this.showResults('ideas'); this.setLoading(true); // Function to fetch new ideas var fetchIdeas = function() { fetch(PFEAI.rest_url + 'article-ideas?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ title: '', post_id: PFEAI.post_id, provider: self.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}).then(function(json){ self.setLoading(false); if(json.ideas) { self.renderIdeasInteractive(json); self.setCachedState('btn-ideas', 'View Topics', 'ideas', Date.now()/1000); } else if (json.code) { self.ideasEl.innerHTML = '

Error: ' + self.escapeHtml(json.message) + '

'; } }).catch(function(e) { self.setLoading(false); console.error(e); }); }; // If forcing refresh, clear cache first, then fetch new ideas if (forceRefresh) { fetch(PFEAI.rest_url + 'clear-ideas-cache?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ post_id: PFEAI.post_id }) }) .then(function() { fetchIdeas(); }) .catch(function() { fetchIdeas(); }); // Still try to fetch even if clear fails } else { fetchIdeas(); } }, /** * IMPROVED: Added better error handling for idea outline */ runIdeaOutline: function(title, context) { var self = this; var resDiv = document.getElementById('pfeai-idea-result'); if(!resDiv) return; resDiv.innerHTML = '

Drafting outline...

This may take up to 30 seconds.

'; // Create abort controller for timeout var controller = new AbortController(); var timeoutId = setTimeout(function() { controller.abort(); }, 180000); // 3 minute timeout fetch(PFEAI.rest_url + 'idea-outline?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ idea_title: title, context: context, provider: this.getProvider() }), signal: controller.signal }) .then(function(r) { clearTimeout(timeoutId); if (!r.ok) { throw new Error('Server returned status ' + r.status); } return r.json(); }) .then(function(json) { console.log('Idea outline response:', json); if (json.code) { resDiv.innerHTML = '

Error: ' + self.escapeHtml(json.message) + '

'; return; } if(json.blurb) { self.renderIdeaOutlineResult(json, title); } else { resDiv.innerHTML = '

AI response was incomplete. Please try again.

'; } }) .catch(function(err) { clearTimeout(timeoutId); console.error('Idea outline error:', err); var errorMsg = 'Error generating outline.'; if (err.name === 'AbortError') { errorMsg = 'Request timed out. Please try again.'; } else if (err.message) { errorMsg = err.message; } resDiv.innerHTML = '

' + self.escapeHtml(errorMsg) + '

'; }); }, runTagline: function(editor) { var self = this; var c = this.getSafeContent(editor); var t = (document.getElementById('title') || {}).value || ''; if (!t) { alert('Enter title first'); return; } // Debug logging console.log('PFEAI Tagline: Content length = ' + c.length); // Show loading state on button var btn = this.panelEl.querySelector('.pfeai-tagline-btn'); var originalText = btn ? btn.textContent : ''; if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } fetch(PFEAI.rest_url + 'generate-tagline?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body:JSON.stringify({content:c, title:t, provider: this.getProvider()}) }) .then(function(r){ console.log('PFEAI Tagline: Response status = ' + r.status); return r.json(); }) .then(function(j){ console.log('PFEAI Tagline: Response = ', j); // Restore button state if (btn) { btn.disabled = false; btn.textContent = originalText; } if(j.tagline) { document.getElementById('pftwt_tagline').value = j.tagline; self.startCacheWindow(false); // API call was made } else if (j.code) { alert('Error: ' + j.message); } else { console.warn('PFEAI Tagline: No tagline in response', j); alert('No tagline was generated. Please try again.'); } }) .catch(function(e) { console.error('PFEAI Tagline: Error = ', e); // Restore button state on error if (btn) { btn.disabled = false; btn.textContent = originalText; } alert('Error: ' + e.message); }); }, runTagSuggestions: function(editor) { var self = this; var c = this.getSafeContent(editor); var t = (document.getElementById('title') || {}).value || ''; // Debug: log content length console.log('PFEAI Tags: Content length = ' + c.length + ', Title = ' + t); if (!c || c.length < 50) { alert('Please add more content before generating tags.'); return; } // Show loading state on button var btn = this.panelEl.querySelector('.pfeai-tags-btn'); var originalText = btn ? btn.textContent : ''; if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } fetch(PFEAI.rest_url + 'suggest-tags?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body:JSON.stringify({content:c, title:t, provider: this.getProvider()}) }) .then(function(r){ console.log('PFEAI Tags: Response status = ' + r.status); return r.json(); }) .then(function(j){ console.log('PFEAI Tags: Response = ', j); // Restore button state if (btn) { btn.disabled = false; btn.textContent = originalText; } // Check for error response if (j.code) { alert('Error generating tags: ' + (j.message || j.code)); return; } if(j.tags && j.tags.length > 0 && jQuery) { var $ = jQuery; var tagsAdded = 0; var tagBox = $('#post_tag'); // Method 1: Use WordPress tagBox API if available if (window.tagBox && typeof window.tagBox.flushTags === 'function') { var $input = $('#new-tag-post_tag'); j.tags.forEach(function(tag) { $input.val(tag); // Trigger the tag addition window.tagBox.flushTags(tagBox, false, 1); tagsAdded++; }); } else { // Method 2: Manually add tags and trigger events var $input = $('#new-tag-post_tag'); var $addBtn = $('#post_tag .tagadd'); j.tags.forEach(function(tag, index) { // Small delay between additions to let WordPress process each one setTimeout(function() { $input.val(tag); $addBtn.trigger('click'); // Also try triggering the keypress enter event as backup var e = $.Event('keypress'); e.which = 13; // Enter key $input.trigger(e); tagsAdded++; // On last tag, show success message if (index === j.tags.length - 1) { setTimeout(function() { // Force refresh the tag cloud display if (window.tagBox && typeof window.tagBox.get === 'function') { window.tagBox.get('post_tag'); } console.log('PFEAI Tags: Added ' + j.tags.length + ' tags'); }, 100); } }, index * 50); // 50ms delay between each tag }); } self.startCacheWindow(false); // API call was made // Show feedback to user if (btn) { btn.textContent = 'Added ' + j.tags.length + ' tags!'; setTimeout(function() { btn.textContent = originalText; }, 2000); } console.log('PFEAI Tags: Added ' + j.tags.length + ' tags'); } else { console.warn('PFEAI Tags: No tags returned or empty array', j); alert('No tags were generated. Please try again.'); } }) .catch(function(e) { console.error('PFEAI Tags: Error = ', e); // Restore button state on error if (btn) { btn.disabled = false; btn.textContent = originalText; } alert('Error: ' + e.message); }); }, /** * Run Meta Description from the main menu button * Shows loading on the menu button and navigates to SEO tab with result */ runMetaDescFromMenu: function(editor) { var self = this; var title = (document.getElementById('title') || {}).value || ''; var content = this.getSafeContent(editor); if (!title && !content) { alert('Please add a title or content first.'); return; } // Show loading state on the menu button var btn = this.panelEl.querySelector('.pfeai-meta-btn'); var originalText = btn ? btn.textContent : ''; if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } fetch(PFEAI.rest_url + 'generate-meta-desc?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body: JSON.stringify({content: content, title: title, provider: this.getProvider()}) }) .then(function(r){return r.json()}) .then(function(j){ // Restore button state if (btn) { btn.disabled = false; btn.textContent = originalText; } if(j.meta_description) { // Navigate to SEO tab and show result self.showResults('seo'); // Fill in the meta description textarea var textarea = document.getElementById('pfeai-meta-description'); if (textarea) { textarea.value = j.meta_description; self.updateSerpPreview(); } self.startCacheWindow(false); // API call was made // Show success feedback on button briefly if (btn) { btn.textContent = 'Generated!'; setTimeout(function() { btn.textContent = originalText; }, 2000); } } else if (j.code) { alert('Error: ' + j.message); } else { alert('No meta description was generated. Please try again.'); } }) .catch(function(e) { // Restore button state on error if (btn) { btn.disabled = false; btn.textContent = originalText; } console.error('PFEAI Meta Desc: Error = ', e); alert('Error: ' + e.message); }); }, runMetaDescription: function() { var self = this; var title = (document.getElementById('title') || {}).value || ''; var content = this.getSafeContent(tinymce.activeEditor); if (!title && !content) { alert('Please add a title or content first.'); return; } var btn = document.getElementById('pfeai-generate-meta-btn'); if (btn) { btn.textContent = 'Generating...'; btn.disabled = true; } fetch(PFEAI.rest_url + 'generate-meta-desc?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body: JSON.stringify({content: content, title: title, provider: this.getProvider()}) }) .then(function(r){return r.json()}) .then(function(j){ if (btn) { btn.innerHTML = '✨ Generate New Meta Description'; btn.disabled = false; } if(j.meta_description) { var textarea = document.getElementById('pfeai-meta-description'); if (textarea) { textarea.value = j.meta_description; self.updateSerpPreview(); } self.startCacheWindow(false); // API call was made } else if (j.code) { alert('Error: ' + j.message); } }) .catch(function(e) { if (btn) { btn.innerHTML = '✨ Generate New Meta Description'; btn.disabled = false; } console.error(e); }); }, runFAQ: function(editor) { var self = this; var c = this.getSafeContent(editor); var t = (document.getElementById('title') || {}).value || ''; if (!c || c.length < 100) { alert('Please write or paste the article content first so the AI can generate accurate FAQs.'); return; } this.showResults('faq'); this.setLoading(true); fetch(PFEAI.rest_url + 'generate-faq?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body: JSON.stringify({content: c, title: t, provider: this.getProvider()}) }) .then(function(r){ return r.json(); }) .then(function(j){ self.setLoading(false); if(j.faq && j.faq.length > 0) { self.renderFAQ(j.faq, editor); self.startCacheWindow(false); } else if (j.code) { self.faqEl.innerHTML = '

Error: ' + self.escapeHtml(j.message) + '

'; } else { self.faqEl.innerHTML = '

Failed to generate FAQ. Please try again.

'; } }) .catch(function(e) { self.setLoading(false); self.faqEl.innerHTML = '

Error: ' + self.escapeHtml(e.message) + '

'; }); }, renderFAQ: function(faqData, editor) { var self = this; if (!this.faqEl) return; this.faqEl.innerHTML = ''; var header = document.createElement('h4'); header.style.margin = '0 0 10px 0'; header.textContent = 'Review & Edit FAQ'; this.faqEl.appendChild(header); var instruction = document.createElement('p'); instruction.style.fontSize = '11px'; instruction.style.color = '#666'; instruction.textContent = 'Review your FAQs below. Clicking the button will send them down to the "Article FAQ & Schema" box at the bottom of your post screen. Remember to click the WordPress Update/Publish button to save them!'; this.faqEl.appendChild(instruction); var faqContainer = document.createElement('div'); faqContainer.id = 'pfeai-faq-inputs-container'; // Build editable inputs for each Q&A in the sidebar faqData.forEach(function(item, index) { var box = document.createElement('div'); box.style.background = '#fff'; box.style.padding = '10px'; box.style.marginBottom = '10px'; box.style.border = '1px solid #ddd'; box.style.borderRadius = '4px'; var qLabel = document.createElement('strong'); qLabel.style.fontSize = '11px'; qLabel.textContent = 'Question ' + (index + 1); box.appendChild(qLabel); var qInput = document.createElement('input'); qInput.type = 'text'; qInput.className = 'pfeai-faq-q'; qInput.style.width = '100%'; qInput.style.marginBottom = '8px'; qInput.value = item.question || ''; box.appendChild(qInput); var aLabel = document.createElement('strong'); aLabel.style.fontSize = '11px'; aLabel.textContent = 'Answer ' + (index + 1); box.appendChild(aLabel); var aInput = document.createElement('textarea'); aInput.className = 'pfeai-faq-a'; aInput.style.width = '100%'; aInput.style.height = '50px'; aInput.style.fontSize = '12px'; aInput.value = item.answer || ''; box.appendChild(aInput); faqContainer.appendChild(box); }); this.faqEl.appendChild(faqContainer); // The Button to transfer text to the Meta Box var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-primary'; insertBtn.style.width = '100%'; insertBtn.innerHTML = '↓ Send to FAQ Meta Box'; insertBtn.onclick = function() { var qInputs = faqContainer.querySelectorAll('.pfeai-faq-q'); var aInputs = faqContainer.querySelectorAll('.pfeai-faq-a'); var transferCount = 0; for (var i = 0; i < qInputs.length; i++) { var qText = qInputs[i].value.trim(); var aText = aInputs[i].value.trim(); // Locate the PHP Meta Box inputs on the main post screen var metaQ = document.getElementById('pfeai_meta_q_' + i); var metaA = document.getElementById('pfeai_meta_a_' + i); if (metaQ && metaA) { metaQ.value = qText; metaA.value = aText; if (qText && aText) transferCount++; } } if (transferCount === 0) { alert('Could not locate the FAQ Meta Box on the screen. Please ensure "Article FAQ & Schema" is checked in your Screen Options at the top right.'); return; } insertBtn.innerHTML = '✓ Sent to Meta Box!'; setTimeout(function() { insertBtn.innerHTML = '↓ Send to FAQ Meta Box'; }, 2000); }; this.faqEl.appendChild(insertBtn); }, updateSerpPreview: function(selectedTitle) { var titleEl = document.getElementById('pfeai-serp-title'); var descEl = document.getElementById('pfeai-serp-desc'); var charCount = document.getElementById('pfeai-meta-char-count'); var titleCharNote = document.getElementById('pfeai-title-char-note'); var metaTextarea = document.getElementById('pfeai-meta-description'); // Update title if provided if (selectedTitle && titleEl) { titleEl.textContent = selectedTitle; // Truncate if too long (Google truncates around 60 chars) if (selectedTitle.length > 60) { titleEl.textContent = selectedTitle.substring(0, 57) + '...'; } } // Update title character count if (titleCharNote && titleEl) { var currentTitle = selectedTitle || titleEl.textContent; if (currentTitle === '(Select a title above)') currentTitle = ''; var titleLen = currentTitle.length; var titleColor = titleLen <= 60 ? '#2e7d32' : '#c62828'; titleCharNote.innerHTML = 'Title: ' + titleLen + '/60 chars ' + (titleLen <= 60 ? '✓' : '(may be truncated)'); } // Update description if (metaTextarea && descEl) { var desc = metaTextarea.value; if (desc) { descEl.textContent = desc.length > 155 ? desc.substring(0, 152) + '...' : desc; } else { descEl.textContent = '(Enter a meta description above)'; } } // Update character count with color coding if (metaTextarea && charCount) { var len = metaTextarea.value.length; var color = '#666'; var status = ''; if (len === 0) { color = '#666'; } else if (len < 120) { color = '#e65100'; // Orange - too short status = ' (too short)'; } else if (len >= 120 && len <= 155) { color = '#2e7d32'; // Green - optimal status = ' ✓ optimal'; } else if (len > 155 && len <= 160) { color = '#e65100'; // Orange - slightly long status = ' (slightly long)'; } else { color = '#c62828'; // Red - too long status = ' (will be truncated)'; } charCount.innerHTML = '' + len + ' / 155 characters' + status + ''; } }, saveToClipboard: function(title, html, redirectToNew) { fetch(PFEAI.rest_url + 'save-clipboard?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ title: title, content: html }) }) .then(function(){ if (redirectToNew) { // Redirect to new post page window.open('/patriots/blog/wp-admin/post-new.php', '_blank'); } else { alert('Saved! You can now access this draft in the "Saved" tab on any new post.'); location.reload(); } }); }, clearClipboard: function() { fetch(PFEAI.rest_url + 'clear-clipboard?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce } }) .then(function(r) { return r.json(); }) .then(function(json) { if (json.success) { alert('Saved draft cleared.'); location.reload(); } }) .catch(function(e) { console.error(e); }); }, clearIdeasCache: function() { fetch(PFEAI.rest_url + 'clear-ideas-cache?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ post_id: PFEAI.post_id }) }) .then(function(r) { return r.json(); }) .then(function(json) { if (json.success) { alert('Topics cache cleared.'); location.reload(); } }) .catch(function(e) { console.error(e); }); }, togglePanel: function(forceState) { if (!this.panelEl) return; var open = this.panelEl.classList.contains('pfeai-open'); var shouldOpen = (typeof forceState === 'boolean') ? forceState : !open; if (shouldOpen) { this.panelEl.classList.add('pfeai-open'); document.body.classList.add('pfeai-panel-open'); this.updateTranscriptVisibility(); if (this.isMinimized) document.body.classList.add('pfeai-panel-minimized'); else document.body.classList.remove('pfeai-panel-minimized'); } else { this.panelEl.classList.remove('pfeai-open'); document.body.classList.remove('pfeai-panel-open'); } }, showMenu: function() { this.panelEl.classList.remove('pfeai-show-results'); }, showResults: function(activeTab) { this.panelEl.classList.add('pfeai-show-results'); if (activeTab) this.switchTab(activeTab); }, updateTranscriptVisibility: function() { var isTranscript = false; var labels = document.querySelectorAll('.categorychecklist label'); for (var i = 0; i < labels.length; i++) { if (labels[i].innerText.indexOf('Patriots Transcripts') !== -1) { var input = labels[i].querySelector('input[type="checkbox"]'); if (input && input.checked) isTranscript = true; } } var btn = document.getElementById('btn-transcript'); var tab = document.getElementById('tab-transcript'); if (isTranscript) { if (btn) btn.style.display = 'block'; if (tab) tab.style.display = 'inline-block'; } else { if (btn) btn.style.display = 'none'; if (tab) tab.style.display = 'none'; } }, preloadSavedData: function() { if (!PFEAI.saved_data) return; if (PFEAI.saved_data.transcript && PFEAI.saved_data.transcript.suggestions) { this.renderTranscriptSuggestions(PFEAI.saved_data.transcript.suggestions, ''); this.setCachedState('btn-transcript', 'View Transcript Results', 'transcript', PFEAI.saved_data.transcript.generated_at); } if (PFEAI.saved_data.analysis && PFEAI.saved_data.analysis.highlights) { this.renderAnalysis(PFEAI.saved_data.analysis); this.setCachedState('btn-scan', 'View Analysis Results', 'highlights', PFEAI.saved_data.analysis.generated_at); } if (PFEAI.saved_data.titles && PFEAI.saved_data.titles.titles) { this.renderTitles(PFEAI.saved_data.titles); this.setCachedState('btn-titles', 'View Titles', 'titles', PFEAI.saved_data.titles.generated_at); } if (PFEAI.saved_data.ideas && PFEAI.saved_data.ideas.ideas) { this.renderIdeasInteractive(PFEAI.saved_data.ideas); this.setCachedState('btn-ideas', 'View Topics', 'ideas', PFEAI.saved_data.ideas.generated_at); } if (PFEAI.saved_data.social && PFEAI.saved_data.social.tweets) { this.renderSocialPack(PFEAI.saved_data.social); this.setCachedState('btn-social', 'View Social Posts', 'social', PFEAI.saved_data.social.generated_at); } if (PFEAI.saved_data.clipboard && this.savedEl) { this.renderSavedDraft(PFEAI.saved_data.clipboard); } if (PFEAI.saved_data.fact_check && PFEAI.saved_data.fact_check.factChecks) { this.renderFacts(PFEAI.saved_data.fact_check.factChecks); this.setCachedState('btn-fact-check', 'View Fact Check', 'facts', PFEAI.saved_data.fact_check.generated_at); } if (PFEAI.saved_data.links && PFEAI.saved_data.links.links) { this.renderInternalLinks(PFEAI.saved_data.links, null); this.setCachedState('btn-internal-links', '\uD83D\uDD17 View Links', 'links', PFEAI.saved_data.links.generated_at); } // Always render the SEO tab this.renderSeoTab(); // Render the Search Insights data this.renderGSCPanel(); }, setCachedState: function(btnId, newText, tabName, timestamp) { var btn = document.getElementById(btnId); if (btn) { if (btnId === 'btn-transcript' || btnId === 'btn-ideas' || btnId === 'btn-social') { btn.innerHTML = '' + this.escapeHtml(newText) + '
(Click to see saved)'; } else { btn.textContent = newText; } } var container = this.panelEl.querySelector('.pfeai-tab-content[data-tab="' + tabName + '"]'); if (container) { var existingHeader = container.querySelector('.pfeai-cache-header'); if (existingHeader) existingHeader.remove(); var dateStr = timestamp ? new Date(timestamp * 1000).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}) : ''; var header = document.createElement('div'); header.className = 'pfeai-cache-header'; header.style.background = '#e1f5fe'; header.style.padding = '8px'; header.style.marginBottom = '10px'; header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; header.style.borderRadius = '4px'; var savedSpan = document.createElement('span'); savedSpan.style.fontSize = '11px'; savedSpan.style.color = '#0277bd'; savedSpan.textContent = 'Saved ' + dateStr; header.appendChild(savedSpan); var rescanBtn = document.createElement('button'); rescanBtn.className = 'button button-small'; rescanBtn.textContent = 'Rescan / Start Over'; (function(name) { rescanBtn.onclick = function() { if (name === 'highlights') window.PFEditorialAI.runAnalysis(tinymce.activeEditor, true); if (name === 'transcript') window.PFEditorialAI.runTranscriptScan(tinymce.activeEditor, true); if (name === 'titles') window.PFEditorialAI.runTitles(tinymce.activeEditor, true); if (name === 'ideas') window.PFEditorialAI.runArticleIdeas(tinymce.activeEditor, true); if (name === 'social') window.PFEditorialAI.runSocialPack(tinymce.activeEditor, true); if (name === 'facts') window.PFEditorialAI.runFactCheck(tinymce.activeEditor, true); if (name === 'links') window.PFEditorialAI.runInternalLinks(tinymce.activeEditor, true); }; })(tabName); header.appendChild(rescanBtn); container.insertBefore(header, container.firstChild); } }, /** * SECURITY FIX: renderAnalysis now uses proper escaping */ renderAnalysis: function(data) { var self = this; var hEl = this.analysisEl.highlights; hEl.innerHTML = ''; (data.highlights || []).forEach(function(h) { var c = document.createElement('div'); c.className = 'pfeai-card'; var typeEl = document.createElement('strong'); typeEl.textContent = (h.type || '').toUpperCase(); c.appendChild(typeEl); var msgEl = document.createElement('p'); msgEl.textContent = h.message || ''; c.appendChild(msgEl); var quoteEl = document.createElement('div'); quoteEl.className = 'pfeai-card-quote'; quoteEl.textContent = '"' + (h.quote || '') + '"'; c.appendChild(quoteEl); var suggEl = document.createElement('div'); suggEl.className = 'pfeai-card-suggestion'; suggEl.textContent = h.suggestion || ''; c.appendChild(suggEl); hEl.appendChild(c); }); var sEl = this.analysisEl.suggestions; sEl.innerHTML = ''; var ulS = document.createElement('ul'); ulS.className = 'pfeai-list'; (data.summarySuggestions || []).forEach(function(s){ var li = document.createElement('li'); li.textContent = s; // SECURITY: textContent escapes HTML ulS.appendChild(li); }); sEl.appendChild(ulS); var gEl = this.analysisEl.gaps; gEl.innerHTML = ''; var ulG = document.createElement('ul'); ulG.className = 'pfeai-list'; (data.contentGaps || []).forEach(function(g){ var li = document.createElement('li'); li.textContent = g; ulG.appendChild(li); }); gEl.appendChild(ulG); // === FACT CHECKS === var fEl = self.factsEl; if (fEl) { fEl.innerHTML = ''; var checks = data.factChecks || []; var factsHeading = document.createElement('h4'); factsHeading.style.margin = '0 0 4px 0'; factsHeading.style.color = '#1A2635'; factsHeading.textContent = 'Stats & Data Verification'; fEl.appendChild(factsHeading); var factsNote = document.createElement('p'); factsNote.style.fontSize = '11px'; factsNote.style.color = '#666'; factsNote.style.margin = '0 0 12px 0'; factsNote.textContent = 'AI-reviewed claims found in the article. Always cross-check flagged items before publishing.'; fEl.appendChild(factsNote); if (checks.length === 0) { var noFacts = document.createElement('p'); noFacts.style.color = '#666'; noFacts.style.fontSize = '13px'; noFacts.textContent = 'No specific stats or data claims detected in this article.'; fEl.appendChild(noFacts); } else { // Summary badge row var counts = { 'Likely Accurate': 0, 'Needs Verification': 0, 'Potentially Incorrect': 0, 'May Be Outdated': 0 }; checks.forEach(function(fc) { if (counts[fc.verdict] !== undefined) counts[fc.verdict]++; }); var badgeRow = document.createElement('div'); badgeRow.style.cssText = 'display:flex; flex-wrap:wrap; gap:6px; margin-bottom:12px;'; var badgeDefs = [ { key: 'Likely Accurate', bg: '#e8f5e9', color: '#2e7d32', icon: '✓' }, { key: 'Needs Verification', bg: '#fff8e1', color: '#f57c00', icon: 'âš ' }, { key: 'May Be Outdated', bg: '#e3f2fd', color: '#1565c0', icon: '📅' }, { key: 'Potentially Incorrect', bg: '#ffebee', color: '#c62828', icon: '✗' } ]; badgeDefs.forEach(function(bd) { if (!counts[bd.key]) return; var badge = document.createElement('span'); badge.style.cssText = 'background:' + bd.bg + '; color:' + bd.color + '; border:1px solid ' + bd.color + '; border-radius:12px; padding:2px 8px; font-size:11px; font-weight:700;'; badge.textContent = bd.icon + ' ' + counts[bd.key] + ' ' + bd.key; badgeRow.appendChild(badge); }); fEl.appendChild(badgeRow); var styleMap = { 'Likely Accurate': { border: '#4caf50', bg: '#f9fbe7', tagBg: '#e8f5e9', tagColor: '#2e7d32', icon: '✓' }, 'Needs Verification': { border: '#ff9800', bg: '#fffde7', tagBg: '#fff3e0', tagColor: '#e65100', icon: 'âš ' }, 'May Be Outdated': { border: '#2196f3', bg: '#e8f4fd', tagBg: '#e3f2fd', tagColor: '#1565c0', icon: '📅' }, 'Potentially Incorrect': { border: '#f44336', bg: '#fff5f5', tagBg: '#ffebee', tagColor: '#c62828', icon: '✗' } }; checks.forEach(function(fc) { var verdict = fc.verdict || 'Needs Verification'; var s = styleMap[verdict] || styleMap['Needs Verification']; var card = document.createElement('div'); card.className = 'pfeai-fact-card'; card.style.cssText = 'background:' + s.bg + '; border-left:4px solid ' + s.border + '; border-radius:3px; padding:8px 10px; margin-bottom:8px;'; var tag = document.createElement('span'); tag.style.cssText = 'display:inline-block; background:' + s.tagBg + '; color:' + s.tagColor + '; border:1px solid ' + s.border + '; border-radius:10px; padding:1px 7px; font-size:10px; font-weight:700; margin-bottom:5px;'; tag.textContent = s.icon + ' ' + verdict; card.appendChild(tag); var statEl = document.createElement('div'); statEl.style.cssText = 'font-size:13px; font-weight:600; color:#1A2635; font-style:italic; margin-bottom:4px;'; statEl.textContent = '\u201c' + (fc.stat || '') + '\u201d'; card.appendChild(statEl); var noteEl = document.createElement('div'); noteEl.style.cssText = 'font-size:12px; color:#444; line-height:1.4;'; noteEl.textContent = fc.note || ''; card.appendChild(noteEl); fEl.appendChild(card); }); } // Update the Facts tab label with a warning badge count var flagged = checks.filter(function(fc) { return fc.verdict === 'Needs Verification' || fc.verdict === 'Potentially Incorrect' || fc.verdict === 'May Be Outdated'; }).length; var factsTab = self.panelEl ? self.panelEl.querySelector('.pfeai-tab[data-tab="facts"]') : null; if (factsTab) { factsTab.innerHTML = flagged > 0 ? '🔍 Facts ' + flagged + '' : '🔍 Facts ✓'; } } }, /** * SECURITY FIX: renderTitles now uses proper escaping */ renderTitles: function(data) { var self = this; this.titlesEl.innerHTML = ''; (data.titles || []).forEach(function(t) { var c = document.createElement('div'); c.className = 'pfeai-title-card'; c.style.display = 'flex'; c.style.flexDirection = 'column'; c.style.gap = '8px'; var td = document.createElement('div'); td.className = 'pfeai-title-text'; td.textContent = t.title; // SECURITY: textContent escapes HTML c.appendChild(td); var sr = document.createElement('div'); sr.className = 'pfeai-score-row'; var ctrSpan = document.createElement('span'); ctrSpan.innerHTML = 'CTR: ' + parseInt(t.ctr_score || 0, 10) + ''; sr.appendChild(ctrSpan); var discSpan = document.createElement('span'); discSpan.innerHTML = 'Disc: ' + parseInt(t.discover_score || 0, 10) + ''; sr.appendChild(discSpan); c.appendChild(sr); var btn = document.createElement('button'); btn.className = 'button button-small'; btn.textContent = 'Use This Title'; btn.style.alignSelf = 'flex-start'; (function(titleText) { btn.onclick = function() { var wt = document.getElementById('title'); if(wt) { wt.value = titleText; wt.focus(); var pt = document.getElementById('title-prompt-text'); if(pt) pt.classList.add('screen-reader-text'); btn.textContent = 'Updated!'; setTimeout(function(){ btn.textContent = 'Use This Title'; }, 2000); } // Update SERP preview with selected title self.updateSerpPreview(titleText); }; })(t.title); c.appendChild(btn); self.titlesEl.appendChild(c); }); }, /** * Render the SEO Meta Description tab */ renderSeoTab: function() { var self = this; if (!this.seoEl) return; this.seoEl.innerHTML = ''; // Get current title from the editor var currentTitle = (document.getElementById('title') || {}).value || ''; var serpHeader = document.createElement('h4'); serpHeader.style.margin = '0 0 15px 0'; serpHeader.style.color = '#1A2635'; serpHeader.textContent = 'SEO Meta Description'; this.seoEl.appendChild(serpHeader); var generateBtn = document.createElement('button'); generateBtn.type = 'button'; generateBtn.className = 'button button-primary'; generateBtn.id = 'pfeai-generate-meta-btn'; generateBtn.innerHTML = '✨ Generate New Meta Description'; generateBtn.style.marginBottom = '15px'; generateBtn.style.width = '100%'; generateBtn.onclick = function() { self.runMetaDescription(); }; this.seoEl.appendChild(generateBtn); var metaTextarea = document.createElement('textarea'); metaTextarea.id = 'pfeai-meta-description'; metaTextarea.style.width = '100%'; metaTextarea.style.height = '80px'; metaTextarea.style.fontSize = '12px'; metaTextarea.style.marginBottom = '5px'; metaTextarea.placeholder = 'Enter or generate a meta description (150-155 characters ideal)...'; metaTextarea.oninput = function() { self.updateSerpPreview(); }; this.seoEl.appendChild(metaTextarea); var charCount = document.createElement('div'); charCount.id = 'pfeai-meta-char-count'; charCount.style.fontSize = '11px'; charCount.style.marginBottom = '10px'; charCount.style.color = '#666'; charCount.textContent = '0 / 155 characters'; this.seoEl.appendChild(charCount); // Insert into Excerpt button var insertExcerptBtn = document.createElement('button'); insertExcerptBtn.type = 'button'; insertExcerptBtn.className = 'button'; insertExcerptBtn.id = 'pfeai-insert-excerpt-btn'; insertExcerptBtn.innerHTML = '→ Transfer to Article Excerpt Block'; insertExcerptBtn.style.marginBottom = '20px'; insertExcerptBtn.style.width = '100%'; insertExcerptBtn.onclick = function() { var metaTextarea = document.getElementById('pfeai-meta-description'); var excerptBox = document.getElementById('excerpt'); if (!metaTextarea || !metaTextarea.value.trim()) { alert('Please generate or enter a meta description first.'); return; } if (excerptBox) { excerptBox.value = metaTextarea.value; insertExcerptBtn.innerHTML = '✓ Inserted!'; setTimeout(function() { insertExcerptBtn.innerHTML = '→ Insert into Excerpt'; }, 2000); } else { alert('Excerpt box not found. Make sure the Excerpt panel is enabled in Screen Options.'); } }; this.seoEl.appendChild(insertExcerptBtn); // SERP Preview section var previewLabel = document.createElement('div'); previewLabel.style.fontSize = '11px'; previewLabel.style.color = '#666'; previewLabel.style.marginBottom = '5px'; previewLabel.textContent = 'Google Search Preview:'; this.seoEl.appendChild(previewLabel); var serpPreview = document.createElement('div'); serpPreview.id = 'pfeai-serp-preview'; serpPreview.style.background = '#fff'; serpPreview.style.border = '1px solid #ddd'; serpPreview.style.borderRadius = '8px'; serpPreview.style.padding = '12px'; serpPreview.style.fontFamily = 'Arial, sans-serif'; var serpTitle = document.createElement('div'); serpTitle.id = 'pfeai-serp-title'; serpTitle.style.color = '#1a0dab'; serpTitle.style.fontSize = '18px'; serpTitle.style.lineHeight = '1.3'; serpTitle.style.marginBottom = '3px'; serpTitle.style.cursor = 'pointer'; serpTitle.textContent = currentTitle || '(No title set)'; serpPreview.appendChild(serpTitle); var serpUrl = document.createElement('div'); serpUrl.style.color = '#006621'; serpUrl.style.fontSize = '13px'; serpUrl.style.marginBottom = '3px'; serpUrl.innerHTML = 'www.patsfans.com › patriots › blog'; serpPreview.appendChild(serpUrl); var serpDesc = document.createElement('div'); serpDesc.id = 'pfeai-serp-desc'; serpDesc.style.color = '#545454'; serpDesc.style.fontSize = '13px'; serpDesc.style.lineHeight = '1.4'; serpDesc.textContent = '(Enter a meta description above)'; serpPreview.appendChild(serpDesc); this.seoEl.appendChild(serpPreview); // Title character count var titleCharNote = document.createElement('div'); titleCharNote.id = 'pfeai-title-char-note'; titleCharNote.style.fontSize = '10px'; titleCharNote.style.color = '#666'; titleCharNote.style.marginTop = '8px'; this.seoEl.appendChild(titleCharNote); // Update the preview with current title this.updateSerpPreview(currentTitle); }, /** * SECURITY FIX: renderTranscriptSuggestions now uses proper escaping */ renderTranscriptSuggestions: function(suggestions, contentText) { var self = this; this.transcriptEl.innerHTML = ''; var h3 = document.createElement('h3'); h3.textContent = 'Suggested Angles'; this.transcriptEl.appendChild(h3); var p = document.createElement('p'); p.style.fontSize = '11px'; p.style.marginBottom = '10px'; p.textContent = 'Click to generate outline.'; this.transcriptEl.appendChild(p); suggestions.forEach(function(item) { var c = document.createElement('div'); c.className = 'pfeai-card pfeai-clickable'; c.style.cursor = 'pointer'; c.style.borderLeftColor = '#e67e22'; var h = document.createElement('h4'); h.style.margin = '0 0 4px 0'; h.style.color = '#d35400'; h.textContent = item.title; // SECURITY: textContent escapes HTML c.appendChild(h); var pEl = document.createElement('p'); pEl.style.fontSize = '12px'; pEl.textContent = item.context; // SECURITY: textContent escapes HTML c.appendChild(pEl); if (item.ctr_score || item.discover_score) { var sr = document.createElement('div'); sr.className = 'pfeai-score-row'; sr.style.marginTop = '6px'; sr.style.borderTop = '1px solid #eee'; sr.style.paddingTop = '4px'; sr.innerHTML = 'CTR: ' + parseInt(item.ctr_score || 0, 10) + 'Disc: ' + parseInt(item.discover_score || 0, 10) + ''; c.appendChild(sr); } (function(title, content) { c.addEventListener('click', function() { self.runTranscriptOutline(title, content, c); }); })(item.title, contentText); self.transcriptEl.appendChild(c); }); var rd = document.createElement('div'); rd.id = 'pfeai-transcript-result'; rd.style.marginTop = '15px'; rd.style.borderTop = '1px solid #ccc'; rd.style.paddingTop = '10px'; this.transcriptEl.appendChild(rd); }, /** * SECURITY FIX: renderTranscriptOutlineResult now uses proper escaping * ENHANCED: Now displays key quotes section */ renderTranscriptOutlineResult: function(data, title) { var self = this; var resDiv = document.getElementById('pfeai-transcript-result'); // Build DOM elements instead of innerHTML for safety var container = document.createElement('div'); container.style.background = '#fff'; container.style.padding = '10px'; container.style.border = '1px solid #ddd'; container.style.marginTop = '10px'; var h3 = document.createElement('h3'); h3.style.marginTop = '0'; h3.textContent = title; // SECURITY: textContent escapes HTML container.appendChild(h3); var blurbLabel = document.createElement('strong'); blurbLabel.textContent = 'Blurb:'; container.appendChild(blurbLabel); var blurbP = document.createElement('p'); blurbP.textContent = data.blurb || ''; // SECURITY: textContent escapes HTML container.appendChild(blurbP); // KEY QUOTES SECTION if (data.key_quotes && data.key_quotes.length > 0) { var quotesDiv = document.createElement('div'); quotesDiv.style.marginTop = '12px'; quotesDiv.style.marginBottom = '12px'; quotesDiv.style.padding = '10px'; quotesDiv.style.background = '#fff8e1'; quotesDiv.style.borderLeft = '4px solid #ffc107'; quotesDiv.style.borderRadius = '4px'; var quotesLabel = document.createElement('strong'); quotesLabel.style.display = 'block'; quotesLabel.style.marginBottom = '8px'; quotesLabel.style.color = '#f57c00'; quotesLabel.innerHTML = '💬 Key Quotes:'; quotesDiv.appendChild(quotesLabel); var quotesList = document.createElement('ul'); quotesList.style.margin = '0'; quotesList.style.paddingLeft = '0'; quotesList.style.listStyleType = 'none'; (data.key_quotes || []).forEach(function(quote) { var li = document.createElement('li'); li.style.marginBottom = '10px'; li.style.fontSize = '12px'; li.style.fontStyle = 'italic'; li.style.color = '#333'; li.style.paddingLeft = '20px'; li.style.position = 'relative'; li.style.lineHeight = '1.4'; // Opening quote mark var openQuote = document.createElement('span'); openQuote.style.position = 'absolute'; openQuote.style.left = '0'; openQuote.style.top = '0'; openQuote.style.color = '#ffc107'; openQuote.style.fontSize = '16px'; openQuote.style.fontWeight = 'bold'; openQuote.style.fontStyle = 'normal'; openQuote.innerHTML = '“'; li.appendChild(openQuote); var quoteText = document.createElement('span'); quoteText.textContent = quote; // SECURITY: textContent escapes HTML li.appendChild(quoteText); var closeQuote = document.createElement('span'); closeQuote.style.color = '#ffc107'; closeQuote.style.fontSize = '16px'; closeQuote.style.fontWeight = 'bold'; closeQuote.style.fontStyle = 'normal'; closeQuote.innerHTML = '”'; li.appendChild(closeQuote); quotesList.appendChild(li); }); quotesDiv.appendChild(quotesList); // Copy all quotes button var copyQuotesBtn = document.createElement('button'); copyQuotesBtn.type = 'button'; copyQuotesBtn.className = 'button button-small'; copyQuotesBtn.style.marginTop = '8px'; copyQuotesBtn.style.background = '#fff3e0'; copyQuotesBtn.style.color = '#e65100'; copyQuotesBtn.style.borderColor = '#ffb74d'; copyQuotesBtn.textContent = 'Copy All Quotes'; copyQuotesBtn.onclick = function() { var allQuotes = (data.key_quotes || []).map(function(q) { return '"' + q + '"'; }).join('\n\n'); navigator.clipboard.writeText(allQuotes); copyQuotesBtn.textContent = 'Copied!'; setTimeout(function() { copyQuotesBtn.textContent = 'Copy All Quotes'; }, 1500); }; quotesDiv.appendChild(copyQuotesBtn); container.appendChild(quotesDiv); } // DRAFT PARAGRAPHS SECTION if (data.draft_paragraphs && data.draft_paragraphs.length > 0) { var draftsDiv = document.createElement('div'); draftsDiv.style.marginTop = '12px'; draftsDiv.style.marginBottom = '12px'; draftsDiv.style.padding = '10px'; draftsDiv.style.background = '#e3f2fd'; draftsDiv.style.borderLeft = '4px solid #2196f3'; draftsDiv.style.borderRadius = '4px'; var draftsLabel = document.createElement('strong'); draftsLabel.style.display = 'block'; draftsLabel.style.marginBottom = '8px'; draftsLabel.style.color = '#1565c0'; draftsLabel.innerHTML = '✍ Draft Paragraphs:'; draftsDiv.appendChild(draftsLabel); var paragraphLabels = ['Opening', 'Quote/Context', 'Analysis']; (data.draft_paragraphs || []).forEach(function(para, idx) { var paraContainer = document.createElement('div'); paraContainer.style.marginBottom = '12px'; paraContainer.style.padding = '8px'; paraContainer.style.background = '#fff'; paraContainer.style.borderRadius = '4px'; paraContainer.style.border = '1px solid #bbdefb'; var paraLabel = document.createElement('div'); paraLabel.style.fontSize = '10px'; paraLabel.style.color = '#1976d2'; paraLabel.style.marginBottom = '4px'; paraLabel.style.fontWeight = 'bold'; paraLabel.textContent = (paragraphLabels[idx] || 'Paragraph ' + (idx + 1)) + ':'; paraContainer.appendChild(paraLabel); var paraText = document.createElement('p'); paraText.style.margin = '0'; paraText.style.fontSize = '12px'; paraText.style.lineHeight = '1.5'; paraText.style.color = '#333'; paraText.textContent = para; paraContainer.appendChild(paraText); // Copy button for individual paragraph var copyParaBtn = document.createElement('button'); copyParaBtn.type = 'button'; copyParaBtn.className = 'button button-small'; copyParaBtn.style.marginTop = '6px'; copyParaBtn.style.fontSize = '10px'; copyParaBtn.textContent = 'Copy'; (function(text, btn) { btn.onclick = function() { navigator.clipboard.writeText(text); btn.textContent = 'Copied!'; setTimeout(function() { btn.textContent = 'Copy'; }, 1500); }; })(para, copyParaBtn); paraContainer.appendChild(copyParaBtn); draftsDiv.appendChild(paraContainer); }); // Insert all drafts button var insertDraftsBtn = document.createElement('button'); insertDraftsBtn.type = 'button'; insertDraftsBtn.className = 'button button-small button-primary'; insertDraftsBtn.style.marginTop = '8px'; insertDraftsBtn.style.marginRight = '8px'; insertDraftsBtn.textContent = 'Insert All Drafts into Editor'; insertDraftsBtn.onclick = function() { if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { var html = (data.draft_paragraphs || []).map(function(p) { return '

' + self.escapeHtml(p) + '

'; }).join('\n'); tinymce.activeEditor.insertContent(html); insertDraftsBtn.textContent = 'Inserted!'; setTimeout(function() { insertDraftsBtn.textContent = 'Insert All Drafts into Editor'; }, 1500); } }; draftsDiv.appendChild(insertDraftsBtn); // Copy all drafts button var copyDraftsBtn = document.createElement('button'); copyDraftsBtn.type = 'button'; copyDraftsBtn.className = 'button button-small'; copyDraftsBtn.style.marginTop = '8px'; copyDraftsBtn.textContent = 'Copy All Drafts'; copyDraftsBtn.onclick = function() { var allDrafts = (data.draft_paragraphs || []).join('\n\n'); navigator.clipboard.writeText(allDrafts); copyDraftsBtn.textContent = 'Copied!'; setTimeout(function() { copyDraftsBtn.textContent = 'Copy All Drafts'; }, 1500); }; draftsDiv.appendChild(copyDraftsBtn); container.appendChild(draftsDiv); } var kpLabel = document.createElement('strong'); kpLabel.textContent = 'Key Points:'; container.appendChild(kpLabel); var kpList = document.createElement('ul'); kpList.className = 'pfeai-list'; (data.key_points || []).forEach(function(pt) { var li = document.createElement('li'); li.textContent = pt; // SECURITY: textContent escapes HTML kpList.appendChild(li); }); container.appendChild(kpList); var sparkDiv = document.createElement('div'); sparkDiv.style.marginTop = '10px'; sparkDiv.style.borderTop = '1px solid #eee'; sparkDiv.style.paddingTop = '10px'; var sparkLabel = document.createElement('strong'); sparkLabel.textContent = 'Spark Sentences:'; sparkDiv.appendChild(sparkLabel); var sparkText = (data.spark_sentences || []).join('\n\n'); var textarea = document.createElement('textarea'); textarea.id = 'pfeai-spark-text'; textarea.style.width = '100%'; textarea.style.height = '100px'; textarea.style.fontSize = '12px'; textarea.style.marginTop = '5px'; textarea.value = sparkText; // SECURITY: .value doesn't execute HTML sparkDiv.appendChild(textarea); var btnDiv = document.createElement('div'); btnDiv.style.marginTop = '8px'; btnDiv.style.display = 'flex'; btnDiv.style.gap = '10px'; var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-small button-primary'; insertBtn.id = 'pfeai-insert-spark'; insertBtn.textContent = 'Insert into Editor'; btnDiv.appendChild(insertBtn); var saveBtn = document.createElement('button'); saveBtn.type = 'button'; saveBtn.className = 'button button-small'; saveBtn.style.background = '#e8f5e9'; saveBtn.style.color = '#2e7d32'; saveBtn.style.borderColor = '#66bb6a'; saveBtn.id = 'pfeai-save-clipboard'; saveBtn.innerHTML = '📝 Start New Post →'; btnDiv.appendChild(saveBtn); sparkDiv.appendChild(btnDiv); container.appendChild(sparkDiv); resDiv.innerHTML = ''; resDiv.appendChild(container); resDiv.scrollIntoView({behavior:'smooth'}); // Bind events insertBtn.onclick = function(){ var wt = document.getElementById('title'); if(wt) { wt.value = title; wt.focus(); var pt = document.getElementById('title-prompt-text'); if(pt) pt.classList.add('screen-reader-text'); } if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { var t = document.getElementById('pfeai-spark-text').value; tinymce.activeEditor.insertContent('

' + self.escapeHtml(t).replace(/\n\n/g, '

') + '

'); } }; saveBtn.onclick = function(){ var fullHTML = ''; if(data.blurb) fullHTML += '

Blurb: ' + self.escapeHtml(data.blurb) + '

'; // Include key quotes in saved draft if(data.key_quotes && data.key_quotes.length) { fullHTML += '

Key Quotes:

    '; data.key_quotes.forEach(function(q){ fullHTML += '
  • “' + self.escapeHtml(q) + '”
  • '; }); fullHTML += '
'; } // Include draft paragraphs in saved draft if(data.draft_paragraphs && data.draft_paragraphs.length) { fullHTML += '

Draft Paragraphs:

'; data.draft_paragraphs.forEach(function(p){ fullHTML += '

' + self.escapeHtml(p) + '

'; }); } if(data.key_points && data.key_points.length) { fullHTML += '

Key Points:

    '; data.key_points.forEach(function(k){ fullHTML += '
  • ' + self.escapeHtml(k) + '
  • '; }); fullHTML += '
'; } var sparkVal = document.getElementById('pfeai-spark-text').value; fullHTML += '

' + self.escapeHtml(sparkVal).replace(/\n\n/g, '

') + '

'; // Save and redirect to new post self.saveToClipboard(title, fullHTML, true); }; }, /** * SECURITY FIX: renderIdeasInteractive now uses proper escaping */ renderIdeasInteractive: function(data) { var self = this; this.ideasEl.innerHTML = ''; var h3 = document.createElement('h3'); h3.textContent = 'Topic Inspiration'; this.ideasEl.appendChild(h3); var p = document.createElement('p'); p.style.fontSize = '11px'; p.style.marginBottom = '10px'; p.textContent = 'Click a topic to generate an outline.'; this.ideasEl.appendChild(p); (data.ideas || []).forEach(function(i) { var c = document.createElement('div'); c.className = 'pfeai-card pfeai-clickable'; c.style.cursor = 'pointer'; c.style.borderLeftColor = '#8e44ad'; var h = document.createElement('h4'); h.style.margin = '0 0 4px 0'; h.style.color = '#8e44ad'; h.textContent = i.title; // SECURITY: textContent escapes HTML c.appendChild(h); var pEl = document.createElement('p'); pEl.style.fontSize = '12px'; pEl.textContent = i.context || i.angle || ''; // SECURITY: textContent escapes HTML c.appendChild(pEl); var sr = document.createElement('div'); sr.className = 'pfeai-score-row'; sr.style.marginTop = '6px'; sr.style.borderTop = '1px solid #eee'; sr.style.paddingTop = '4px'; sr.innerHTML = 'Trend: ' + parseInt(i.trend_score || i.ctr_score || 0, 10) + 'Disc: ' + parseInt(i.discover_score || 0, 10) + ''; c.appendChild(sr); (function(title, context) { c.addEventListener('click', function() { self.runIdeaOutline(title, context); }); })(i.title, i.context); self.ideasEl.appendChild(c); }); var rd = document.createElement('div'); rd.id = 'pfeai-idea-result'; rd.style.marginTop = '15px'; rd.style.borderTop = '1px solid #ccc'; rd.style.paddingTop = '10px'; this.ideasEl.appendChild(rd); }, /** * SECURITY FIX: renderIdeaOutlineResult now uses proper escaping */ renderIdeaOutlineResult: function(data, title) { var self = this; var resDiv = document.getElementById('pfeai-idea-result'); // Build DOM elements instead of innerHTML for safety var container = document.createElement('div'); container.style.background = '#fff'; container.style.padding = '10px'; container.style.border = '1px solid #ddd'; container.style.marginTop = '10px'; var h3 = document.createElement('h3'); h3.style.marginTop = '0'; h3.style.color = '#8e44ad'; h3.textContent = title; // SECURITY: textContent escapes HTML container.appendChild(h3); var blurbLabel = document.createElement('strong'); blurbLabel.textContent = 'Blurb:'; container.appendChild(blurbLabel); var blurbP = document.createElement('p'); blurbP.textContent = data.blurb || ''; // SECURITY: textContent escapes HTML container.appendChild(blurbP); // DRAFT PARAGRAPHS SECTION if (data.draft_paragraphs && data.draft_paragraphs.length > 0) { var draftsDiv = document.createElement('div'); draftsDiv.style.marginTop = '12px'; draftsDiv.style.marginBottom = '12px'; draftsDiv.style.padding = '10px'; draftsDiv.style.background = '#f3e5f5'; draftsDiv.style.borderLeft = '4px solid #9c27b0'; draftsDiv.style.borderRadius = '4px'; var draftsLabel = document.createElement('strong'); draftsLabel.style.display = 'block'; draftsLabel.style.marginBottom = '8px'; draftsLabel.style.color = '#7b1fa2'; draftsLabel.innerHTML = '✍ Draft Paragraphs:'; draftsDiv.appendChild(draftsLabel); var paragraphLabels = ['Opening', 'Middle/Analysis', 'Context/Closing']; (data.draft_paragraphs || []).forEach(function(para, idx) { var paraContainer = document.createElement('div'); paraContainer.style.marginBottom = '12px'; paraContainer.style.padding = '8px'; paraContainer.style.background = '#fff'; paraContainer.style.borderRadius = '4px'; paraContainer.style.border = '1px solid #e1bee7'; var paraLabel = document.createElement('div'); paraLabel.style.fontSize = '10px'; paraLabel.style.color = '#8e44ad'; paraLabel.style.marginBottom = '4px'; paraLabel.style.fontWeight = 'bold'; paraLabel.textContent = (paragraphLabels[idx] || 'Paragraph ' + (idx + 1)) + ':'; paraContainer.appendChild(paraLabel); var paraText = document.createElement('p'); paraText.style.margin = '0'; paraText.style.fontSize = '12px'; paraText.style.lineHeight = '1.5'; paraText.style.color = '#333'; paraText.textContent = para; paraContainer.appendChild(paraText); // Copy button for individual paragraph var copyParaBtn = document.createElement('button'); copyParaBtn.type = 'button'; copyParaBtn.className = 'button button-small'; copyParaBtn.style.marginTop = '6px'; copyParaBtn.style.fontSize = '10px'; copyParaBtn.textContent = 'Copy'; (function(text, btn) { btn.onclick = function() { navigator.clipboard.writeText(text); btn.textContent = 'Copied!'; setTimeout(function() { btn.textContent = 'Copy'; }, 1500); }; })(para, copyParaBtn); paraContainer.appendChild(copyParaBtn); draftsDiv.appendChild(paraContainer); }); // Insert all drafts button var insertDraftsBtn = document.createElement('button'); insertDraftsBtn.type = 'button'; insertDraftsBtn.className = 'button button-small button-primary'; insertDraftsBtn.style.marginTop = '8px'; insertDraftsBtn.style.marginRight = '8px'; insertDraftsBtn.textContent = 'Insert All Drafts into Editor'; insertDraftsBtn.onclick = function() { if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { var html = (data.draft_paragraphs || []).map(function(p) { return '

' + self.escapeHtml(p) + '

'; }).join('\n'); tinymce.activeEditor.insertContent(html); insertDraftsBtn.textContent = 'Inserted!'; setTimeout(function() { insertDraftsBtn.textContent = 'Insert All Drafts into Editor'; }, 1500); } }; draftsDiv.appendChild(insertDraftsBtn); // Copy all drafts button var copyDraftsBtn = document.createElement('button'); copyDraftsBtn.type = 'button'; copyDraftsBtn.className = 'button button-small'; copyDraftsBtn.style.marginTop = '8px'; copyDraftsBtn.textContent = 'Copy All Drafts'; copyDraftsBtn.onclick = function() { var allDrafts = (data.draft_paragraphs || []).join('\n\n'); navigator.clipboard.writeText(allDrafts); copyDraftsBtn.textContent = 'Copied!'; setTimeout(function() { copyDraftsBtn.textContent = 'Copy All Drafts'; }, 1500); }; draftsDiv.appendChild(copyDraftsBtn); container.appendChild(draftsDiv); } var kpLabel = document.createElement('strong'); kpLabel.textContent = 'Key Points:'; container.appendChild(kpLabel); var kpList = document.createElement('ul'); kpList.className = 'pfeai-list'; (data.key_points || []).forEach(function(pt) { var li = document.createElement('li'); li.textContent = pt; // SECURITY: textContent escapes HTML kpList.appendChild(li); }); container.appendChild(kpList); var sparkDiv = document.createElement('div'); sparkDiv.style.marginTop = '10px'; sparkDiv.style.borderTop = '1px solid #eee'; sparkDiv.style.paddingTop = '10px'; var sparkLabel = document.createElement('strong'); sparkLabel.textContent = 'Spark Sentences:'; sparkDiv.appendChild(sparkLabel); var sparkText = (data.spark_sentences || []).join('\n\n'); var textarea = document.createElement('textarea'); textarea.id = 'pfeai-idea-spark-text'; textarea.style.width = '100%'; textarea.style.height = '100px'; textarea.style.fontSize = '12px'; textarea.style.marginTop = '5px'; textarea.value = sparkText; // SECURITY: .value doesn't execute HTML sparkDiv.appendChild(textarea); var btnDiv = document.createElement('div'); btnDiv.style.marginTop = '8px'; btnDiv.style.display = 'flex'; btnDiv.style.gap = '10px'; var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-small button-primary'; insertBtn.id = 'pfeai-insert-idea-spark'; insertBtn.textContent = 'Insert into Editor'; btnDiv.appendChild(insertBtn); var saveBtn = document.createElement('button'); saveBtn.type = 'button'; saveBtn.className = 'button button-small'; saveBtn.style.background = '#e8f5e9'; saveBtn.style.color = '#2e7d32'; saveBtn.style.borderColor = '#66bb6a'; saveBtn.id = 'pfeai-save-idea-clipboard'; saveBtn.innerHTML = '📝 Start New Post →'; btnDiv.appendChild(saveBtn); sparkDiv.appendChild(btnDiv); container.appendChild(sparkDiv); resDiv.innerHTML = ''; resDiv.appendChild(container); resDiv.scrollIntoView({behavior:'smooth'}); // Bind events insertBtn.onclick = function(){ var wt = document.getElementById('title'); if(wt) { wt.value = title; wt.focus(); var pt = document.getElementById('title-prompt-text'); if(pt) pt.classList.add('screen-reader-text'); } if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { var t = document.getElementById('pfeai-idea-spark-text').value; tinymce.activeEditor.insertContent('

' + self.escapeHtml(t).replace(/\n\n/g, '

') + '

'); } }; saveBtn.onclick = function(){ var fullHTML = ''; if(data.blurb) fullHTML += '

Blurb: ' + self.escapeHtml(data.blurb) + '

'; // Include draft paragraphs in saved draft if(data.draft_paragraphs && data.draft_paragraphs.length) { fullHTML += '

Draft Paragraphs:

'; data.draft_paragraphs.forEach(function(p){ fullHTML += '

' + self.escapeHtml(p) + '

'; }); } if(data.key_points && data.key_points.length) { fullHTML += '

Key Points:

    '; data.key_points.forEach(function(k){ fullHTML += '
  • ' + self.escapeHtml(k) + '
  • '; }); fullHTML += '
'; } var sparkVal = document.getElementById('pfeai-idea-spark-text').value; fullHTML += '

' + self.escapeHtml(sparkVal).replace(/\n\n/g, '

') + '

'; // Save and redirect to new post self.saveToClipboard(title, fullHTML, true); }; }, /** * SECURITY FIX: renderSavedDraft now uses proper escaping */ renderSavedDraft: function(clip) { var self = this; if(!this.savedEl) return; var cleanText = (clip.content || '') .replace(/
    /g, '').replace(/<\/ul>/g, '') .replace(/
  • /g, '\u2022 ').replace(/<\/li>/g, '\n') .replace(/

    /g, '').replace(/<\/p>/g, '\n\n') .replace(//g, '').replace(/<\/strong>/g, '') .trim(); // Build DOM elements instead of innerHTML var container = document.createElement('div'); container.style.background = '#e8f5e9'; container.style.padding = '10px'; container.style.border = '1px solid #c8e6c9'; var h3 = document.createElement('h3'); h3.style.marginTop = '0'; h3.style.color = '#2e7d32'; h3.textContent = 'Saved Draft'; container.appendChild(h3); // Content first var contentDiv = document.createElement('div'); var contentLabel = document.createElement('strong'); contentLabel.textContent = 'Content:'; contentDiv.appendChild(contentLabel); contentDiv.appendChild(document.createElement('br')); var contentTextarea = document.createElement('textarea'); contentTextarea.id = 'pfeai-saved-content'; contentTextarea.style.width = '100%'; contentTextarea.style.height = '300px'; contentTextarea.style.fontSize = '12px'; contentTextarea.value = cleanText; // SECURITY: .value doesn't execute HTML contentDiv.appendChild(contentTextarea); container.appendChild(contentDiv); // Title second var titleDiv = document.createElement('div'); titleDiv.style.marginTop = '10px'; titleDiv.style.marginBottom = '10px'; var titleLabel = document.createElement('strong'); titleLabel.textContent = 'Title:'; titleDiv.appendChild(titleLabel); titleDiv.appendChild(document.createElement('br')); var titleInput = document.createElement('input'); titleInput.type = 'text'; titleInput.id = 'pfeai-saved-title'; titleInput.value = clip.title || ''; // SECURITY: .value doesn't execute HTML titleInput.style.width = '100%'; titleInput.style.fontSize = '12px'; titleDiv.appendChild(titleInput); container.appendChild(titleDiv); // Button container var btnContainer = document.createElement('div'); btnContainer.style.marginTop = '10px'; btnContainer.style.display = 'flex'; btnContainer.style.gap = '10px'; var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-primary'; insertBtn.id = 'pfeai-insert-saved'; insertBtn.textContent = 'Insert into Editor'; btnContainer.appendChild(insertBtn); var clearBtn = document.createElement('button'); clearBtn.type = 'button'; clearBtn.className = 'button'; clearBtn.style.background = '#ffebee'; clearBtn.style.color = '#c62828'; clearBtn.style.borderColor = '#ef9a9a'; clearBtn.textContent = 'Clear Saved Draft'; btnContainer.appendChild(clearBtn); container.appendChild(btnContainer); this.savedEl.innerHTML = ''; this.savedEl.appendChild(container); insertBtn.onclick = function() { var t = document.getElementById('pfeai-saved-title').value; var c = clip.content; // Use original HTML content (already sanitized by server) var wt = document.getElementById('title'); if(wt) { wt.value = t; wt.focus(); var pt = document.getElementById('title-prompt-text'); if(pt) pt.classList.add('screen-reader-text'); } if(typeof tinymce !== 'undefined' && tinymce.activeEditor){ tinymce.activeEditor.setContent(c); } }; clearBtn.onclick = function() { if (confirm('Are you sure you want to clear your saved draft? This cannot be undone.')) { self.clearClipboard(); } }; }, toggleMinimize: function() { if (!this.panelEl) return; this.isMinimized = !this.isMinimized; this.panelEl.classList.toggle('pfeai-minimized', this.isMinimized); document.body.classList.toggle('pfeai-panel-minimized', this.isMinimized); }, setLoading: function(isLoading) { if(this.loadingEl) this.loadingEl.style.display = isLoading ? 'block' : 'none'; }, switchTab: function(tabName) { if (!this.panelEl) return; this.panelEl.querySelectorAll('.pfeai-tab').forEach(function(b) { b.classList.toggle('pfeai-tab-active', b.getAttribute('data-tab') === tabName); }); this.panelEl.querySelectorAll('.pfeai-tab-content').forEach(function(c) { c.style.display = (c.getAttribute('data-tab') === tabName) ? 'block' : 'none'; }); // Refresh SEO tab to get current title when switching to it if (tabName === 'seo') { this.renderSeoTab(); } }, /** * Internal Link Suggester * Fetches candidate posts from WP via the REST endpoint, sends to LLM for ranking, * then renders cards with one-click TinyMCE link insertion. */ runInternalLinks: function(editor, forceRefresh) { var self = this; var title = (document.getElementById('title') || {}).value || ''; var contentHtml = this.getSafeContent(editor); if (!contentHtml || contentHtml.length < 100) { alert('Please write or paste article content first.'); return; } if (!PFEAI.post_id) { alert('Please save the post as a draft first. WordPress needs a Post ID to match categories and tags for link candidates.'); return; } this.showResults('links'); this.setLoading(true); fetch(PFEAI.rest_url + 'internal-links?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ content: contentHtml, title: title, post_id: PFEAI.post_id, provider: this.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r) { return r.json(); }) .then(function(json) { self.setLoading(false); if (json.links && json.links.length > 0) { self.renderInternalLinks(json, editor); var timestamp = json.generated_at || Date.now()/1000; self.setCachedState('btn-internal-links', '\uD83D\uDD17 View Links', 'links', timestamp, json.from_cache); self.startCacheWindow(json.from_cache); } else if (json.code) { self.linksEl.innerHTML = '

    Error: ' + self.escapeHtml(json.message) + '

    '; } else { self.linksEl.innerHTML = '

    No strong internal link opportunities found for this article.

    '; } }) .catch(function(e) { self.setLoading(false); if (self.linksEl) { self.linksEl.innerHTML = '

    Error: ' + self.escapeHtml(e.message) + '

    '; } }); }, renderInternalLinks: function(data, editor) { var self = this; if (!this.linksEl) return; this.linksEl.innerHTML = ''; // Header var heading = document.createElement('h4'); heading.style.cssText = 'margin:0 0 4px 0; color:#1A2635;'; heading.textContent = 'Internal Link Suggestions'; this.linksEl.appendChild(heading); var note = document.createElement('p'); note.style.cssText = 'font-size:11px; color:#666; margin:0 0 12px 0;'; var entityNote = (data.entities_found && data.entities_found.length > 0) ? ' Searched by: ' + data.entities_found.join(', ') + '.' : ''; note.textContent = 'Checked ' + (data.candidates_checked || 0) + ' posts across full archive.' + entityNote; this.linksEl.appendChild(note); // Sort High -> Medium -> Low var order = { 'High': 0, 'Medium': 1, 'Low': 2 }; var links = (data.links || []).slice().sort(function(a, b) { return (order[a.priority] || 1) - (order[b.priority] || 1); }); var styleMap = { 'High': { bg: '#fff5f5', border: '#D7192D', tagBg: '#D7192D', tagFg: '#fff' }, 'Medium': { bg: '#fffde7', border: '#ff9800', tagBg: '#ff9800', tagFg: '#fff' }, 'Low': { bg: '#f5f5f5', border: '#9e9e9e', tagBg: '#9e9e9e', tagFg: '#fff' } }; links.forEach(function(link) { var priority = link.priority || 'Medium'; var s = styleMap[priority] || styleMap['Medium']; var card = document.createElement('div'); card.style.cssText = 'background:' + s.bg + '; border-left:4px solid ' + s.border + '; border-radius:3px; padding:10px; margin-bottom:10px;'; var topRow = document.createElement('div'); topRow.style.cssText = 'display:flex; align-items:flex-start; gap:8px; margin-bottom:6px;'; var tag = document.createElement('span'); tag.style.cssText = 'flex-shrink:0; background:' + s.tagBg + '; color:' + s.tagFg + '; border-radius:10px; padding:1px 7px; font-size:10px; font-weight:700; margin-top:2px;'; tag.textContent = priority; topRow.appendChild(tag); var postTitle = document.createElement('strong'); postTitle.style.cssText = 'font-size:12px; line-height:1.35; color:#1A2635;'; postTitle.textContent = link.post_title || ''; topRow.appendChild(postTitle); card.appendChild(topRow); if (link.post_date) { var dateEl = document.createElement('div'); dateEl.style.cssText = 'font-size:10px; color:#888; margin-bottom:5px;'; dateEl.textContent = 'Published: ' + link.post_date; card.appendChild(dateEl); } var anchorRow = document.createElement('div'); anchorRow.style.cssText = 'font-size:12px; margin-bottom:4px;'; var anchorLabel = document.createElement('span'); anchorLabel.style.color = '#666'; anchorLabel.textContent = 'Anchor: '; anchorRow.appendChild(anchorLabel); var chip = document.createElement('code'); chip.style.cssText = 'background:#e8f5e9; color:#2e7d32; padding:1px 5px; border-radius:3px; font-size:12px;'; chip.textContent = link.anchor_text || ''; anchorRow.appendChild(chip); card.appendChild(anchorRow); if (link.target_sentence) { var hint = document.createElement('div'); hint.style.cssText = 'font-size:11px; color:#555; font-style:italic; margin-bottom:4px;'; hint.textContent = 'Near: \u201c' + link.target_sentence + '\u2026\u201d'; card.appendChild(hint); } if (link.reason) { var reason = document.createElement('div'); reason.style.cssText = 'font-size:11px; color:#444; margin-bottom:8px; line-height:1.4;'; reason.textContent = link.reason; card.appendChild(reason); } var btnRow = document.createElement('div'); btnRow.style.cssText = 'display:flex; gap:6px; flex-wrap:wrap;'; var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-small button-primary'; insertBtn.textContent = '\uD83D\uDD17 Insert Link'; (function(anchorText, url, btn) { btn.onclick = function() { var success = false; if (typeof tinymce !== 'undefined' && tinymce.activeEditor && !tinymce.activeEditor.isHidden()) { var body = tinymce.activeEditor.getBody(); var walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false); var node; while ((node = walker.nextNode())) { var idx = node.textContent.indexOf(anchorText); if (idx !== -1) { var rng = tinymce.activeEditor.dom.createRng(); rng.setStart(node, idx); rng.setEnd(node, idx + anchorText.length); tinymce.activeEditor.selection.setRng(rng); tinymce.activeEditor.execCommand('mceInsertLink', false, { href: url }); success = true; break; } } } if (success) { btn.textContent = '\u2713 Linked!'; btn.style.background = '#4caf50'; btn.disabled = true; } else { var html = '' + anchorText + ''; navigator.clipboard.writeText(html); btn.textContent = 'HTML copied (text not found)'; setTimeout(function() { btn.textContent = '\uD83D\uDD17 Insert Link'; }, 3000); } }; })(link.anchor_text, link.post_url, insertBtn); btnRow.appendChild(insertBtn); card.appendChild(btnRow); self.linksEl.appendChild(card); }); }, /** * Render Search Console Data */ /** * Render Search Console Data */ renderGSCPanel: function() { var self = this; // <-- THIS WAS MISSING! var data = PFEAI.saved_data.gsc_data; var container = this.panelEl.querySelector('.pfeai-tab-content[data-tab="search"]'); if (!container) return; if (!data || data.length === 0) { container.innerHTML = '

    No "striking distance" keywords found for this URL yet.
    (Google data usually lags by 48 hours).

    '; return; } let html = '
    '; html += '

    Striking Distance

    '; html += '

    Keywords ranking #7-#20. Push these to Page 1!

    '; data.forEach(item => { html += `
    ${self.escapeHtml(item.keyword)} Pos: ${item.position} | Impr: ${item.impressions}
    `; }); html += '
    '; container.innerHTML = html; // Bind the optimize buttons container.querySelectorAll('.pfeai-optimize-keyword').forEach(btn => { btn.onclick = function() { var keyword = this.getAttribute('data-keyword'); var content = self.getSafeContent(tinymce.activeEditor); var title = (document.getElementById('title') || {}).value || ''; var originalText = this.innerHTML; this.innerHTML = ''; this.disabled = true; // Fetch AI Suggestion fetch(PFEAI.rest_url + 'optimize-keyword?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ keyword: keyword, content: content, title: title, provider: self.getProvider() }) }) .then(r => r.json()) .then(json => { this.innerHTML = originalText; this.disabled = false; if (json.suggestion) { // Create a result box right below the item var resultBox = document.createElement('div'); resultBox.style.cssText = 'margin-top:10px; padding:10px; background:#e8f5e9; border:1px solid #a5d6a7; border-radius:4px; font-size:12px;'; var html = 'Placement Hint: ' + self.escapeHtml(json.placement) + '

    '; html += 'Suggested Addition:

    ' + self.escapeHtml(json.suggestion) + '

    '; var insertBtn = document.createElement('button'); insertBtn.className = 'button button-small button-primary'; insertBtn.textContent = 'Insert into Editor'; insertBtn.onclick = function() { if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { tinymce.activeEditor.insertContent('

    ' + self.escapeHtml(json.suggestion) + '

    '); insertBtn.textContent = 'Inserted!'; setTimeout(() => resultBox.remove(), 2000); } }; resultBox.innerHTML = html; resultBox.appendChild(insertBtn); // Insert the box below the button's parent container this.parentNode.after(resultBox); self.startCacheWindow(false); } else if (json.code) { alert('Error: ' + json.message); } }) .catch(e => { this.innerHTML = originalText; this.disabled = false; alert('Optimization failed: ' + e.message); }); }; }); }, bindTooltipEvents: function() {} }; })();

    More News Headlines:

    Several Remaining Patriots Free Agents Still Seeking Homes

    Several Remaining Patriots Free Agents Still Seeking Homes

    By: Ian Logue
    Patriots free agents Jahlani Tavai, Antonio Gibson, and several others are still seeking homes for the 2026 season. Two former Pats found new teams.
    13 hours ago
    ESPN Insider on Patriots A.J. Brown Trade: ‘I Think He Knows Where His Future is Headed’

    ESPN Insider on Patriots A.J. Brown Trade: ‘I Think He Knows Where His Future is Headed’

    By: Ian Logue
    Adam Schefter is confident an A.J. Brown trade to the Patriots is still on track, expecting a June 1st deal involving a future first-round pick.
    14 hours ago
    Former Patriots Staffer Reveals Surprising Person Behind Two Key Player Cornerstone Additions in 2021

    Former Patriots Staffer Reveals Surprising Person Behind Two Key Player Cornerstone Additions in 2021

    By: Ian Logue
    In a surprising twist, former Patriots staffer Joe Kim credits Matt Patricia for the 2021 draft selections of Rhamondre Stevenson and Christian Barmore.
    2 days ago
    Patriots News 05-03, A.J. Brown Concerns, Vrabel’s Saga

    Patriots News 05-03, A.J. Brown Concerns, Vrabel’s Saga

    By: Steve Balestrieri
    Analyzing Patriots news, including real concerns about a potential A.J. Brown trade, Mike Vrabel's locker room support, and rookie updates.
    3 days ago
    MORSE: Clearing the Notebook from the Patriots Draft

    MORSE: Clearing the Notebook from the Patriots Draft

    By: Mark Morse
    Patriots draft analysis covers PFF grades for Caleb Lomu, Gabe Jacas, and Eli Raridon, plus rookie camp invites and Marshall Oium's departure.
    5 days ago